// Copyright Gary Evans 2006-2007.

using System;
using System.Collections.Generic;
using System.Text;

namespace Taumuon.Jabuka.Physics.Collision
{
    /// <summary>
    /// Represents a Sphere class, for Collision detection.
    /// </summary>
    public class Sphere : ICloneable
    {
        #region Object Lifetime

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="eulerRigidBody">Holds the sphere's location and velocity.</param>
        /// <param name="radius">The sphere's radius</param>
        public Sphere(IEulerRigidBody eulerRigidBody, double radius)
        {
            if (eulerRigidBody == null)
            {
                throw new ArgumentNullException("eulerRigidBody");
            }
            if (radius < 0.0)
            {
                throw new ArgumentException("Radius should not be negative", "radius");
            }
            this.eulerRigidBody = eulerRigidBody;
            this.radius = radius;
        }

        #endregion Object Lifetime

		#region Public Properties

		/// <summary>
		/// Returns the EulerRigidBody instance
		///  that contains details on the object's physical state.
		/// </summary>
		public virtual IEulerRigidBody EulerRigidBody
		{
			get
			{
				return this.eulerRigidBody;
			}
		}

        /// <summary>
        /// Returns the Sphere's radius.
        /// </summary>
        public double Radius
        {
            get
            {
                return this.radius;
            }
        }

		#endregion Public Properties

		#region Public Methods

		/// <summary>
        /// Checks whether this sphere instance is colliding
        ///  with the sphere passed in.
        /// </summary>
        public virtual CollisionType CheckForCollision(Sphere rhs)
        {
            CollisionType result = CollisionType.None;

            Vector vectorThisCentreToOther =
                rhs.eulerRigidBody.Location - this.eulerRigidBody.Location;

            double separation = vectorThisCentreToOther.Magnitude;
            double combinedRadius = this.radius + rhs.radius;

            if ( MathUtils.IsEqualWithinTol(separation, 
                combinedRadius, Tolerance) )
            {
                // The spheres are within touching distance
                // See whether they're moving towards each other.
                // relative velocity of the other body with respect to us.
                Vector relativeVelocity = rhs.eulerRigidBody.Velocity
                    - this.eulerRigidBody.Velocity;
                double speedAwayFromUs = vectorThisCentreToOther.DotProduct(relativeVelocity);
                if (MathUtils.IsEqualWithinTol(speedAwayFromUs, 0.0, Tolerance))
                {
					// the object is not moving towards us, check whether it is stationary
					// or moving away i.e. this is a very near miss)
					if (Math.Abs(relativeVelocity.Magnitude) < Tolerance)
					{
						// We have some sort of contact
						// Either just touching, or being forced towards.
						// NOTE: need to compare relative acceleration - can't just compare forces
						//   as one object may be heavier (not like gravity which is the same
						//   under all forces.)

						// The acceleration of the other body with respect to us.
						Vector relativeAcceleration = rhs.EulerRigidBody.Acceleration
							- this.eulerRigidBody.Acceleration;
						double accelerationAwayFromUs = vectorThisCentreToOther.DotProduct(relativeAcceleration);
						if (MathUtils.IsEqualWithinTol(accelerationAwayFromUs, 0.0, Tolerance))
						{
							// The objects just happen to be in close proximity.
							result = CollisionType.TouchingContact;
						}
						else if (accelerationAwayFromUs < Tolerance)
						{
							// A negative value indicates that the rhs sphere is being accelerated towards us
							result = CollisionType.ForcedContact;
						}
						// else, the sphere is being accelerated away, is no contact or collision.
					}
					// else, object's acceleration is no different to ours, no collision.
                }
                else if (speedAwayFromUs < Tolerance)
                {
                    // A negative value indicates that the rhs sphere is travelling towards us.
                    result = CollisionType.Collision;
                }
                // else, are moving away, no action necessary.
            }
            else if ( separation < combinedRadius )
            {
				result = CollisionType.Intersection;
            }
			// else, the spheres are separated, no collision.

            return result;
        }

		/// <summary>
		/// Assigns the values of this sphere and the values of its members
		///  from the values on the sphere passed in.
		/// </summary>
		/// <param name="rhs">The sphere to set the values from</param>
		/// <remarks>This sets all values, analogous to a deep copy.
		/// It does not change any of the original objects' or sub objects' references
		///  (only sets values on those references)</remarks>
		public virtual void AssignFromSphere(Sphere rhs)
		{
			this.radius = rhs.radius;
			this.eulerRigidBody.AssignFromEulerRigidBody(rhs.EulerRigidBody);
		}

        #endregion Public Methods

        #region ICloneable Members

        /// <summary>
        /// Performs a deep copy.
        /// </summary>
        /// <returns>A copy of the object.</returns>
        public virtual object Clone()
        {
            Sphere sphere = this.MemberwiseClone() as Sphere;
            sphere.eulerRigidBody = 
                this.eulerRigidBody.Clone() as IEulerRigidBody;
            return sphere;
        }

        #endregion ICloneable Members

		#region Member Variables

		private IEulerRigidBody eulerRigidBody;
        private const double Tolerance = 1e-10;
        private double radius;

		#endregion Member Variables
	}
}
